#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/dma-mapping.h>

#define MODULENAME "dmadriver"
#define LOG_INFO(fmt, ...) \
    printk(KERN_INFO MODULENAME ": " fmt, ##__VA_ARGS__)

#define LOG_ERR(fmt, ...) \
    printk(KERN_ERR MODULENAME ": " fmt, ##__VA_ARGS__)

#define DEVICE_NAME MODULENAME
#define MEM_SIZE (1 * 1024 * 1024) // 1MB

static dev_t dev_num;
static struct cdev *my_cdev = NULL;
static struct class *mychardev_class = NULL;
static struct device *mychardev_device = NULL;

static struct page *pages = NULL;
static char *mem_buffer;

static int my_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    return 0;
}

static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    return 0;
}

static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;

    if ((vma->vm_end - vma->vm_start) > MEM_SIZE)
    {
        printk(KERN_ERR "Mapping size exceeds memory buffer size.\n");
        return -EINVAL;
    }

    // 将物理内存映射到用户空间
    ret = remap_pfn_range(vma, vma->vm_start, virt_to_phys(mem_buffer) >> PAGE_SHIFT,
                          vma->vm_end - vma->vm_start, vma->vm_page_prot);
    if (ret)
    {
        printk(KERN_ERR "Failed to mmap physical memory.\n");
        return -EAGAIN;
    }

    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
    .mmap = my_mmap,
};

static int create_mem_buffer(void)
{
    // 预申请1MB物理内存
    pages = alloc_pages(GFP_KERNEL | GFP_DMA, get_order(MEM_SIZE));
    if (!pages)
    {
        printk(KERN_ERR "Failed to allocate pages.\n");
        return -ENOMEM;
    }
    // 将页面映射到虚拟地址空间中
    mem_buffer = (char *)page_address(pages);
    if (!mem_buffer)
    {
        free_pages((unsigned long)pages, get_order(MEM_SIZE));
        printk(KERN_ERR "Failed to map memory pages.\n");
        return -EFAULT;
    }
    return 0;
}

static int __init my_char_dev_init(void)
{
    int ret;

    LOG_INFO("my_char_dev_init\n");

    // 分配字符设备号
    if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0)
    {
        printk(KERN_ERR "Failed to allocate character device number.\n");
        return -EFAULT;
    }

    LOG_INFO("my_char_dev_init: major = %d, minor = %d\n", MAJOR(dev_num), MINOR(dev_num));

    // 创建字符设备结构体
    my_cdev = cdev_alloc();
    if (!my_cdev)
    {
        printk(KERN_ERR "Failed to allocate cdev structure.\n");
        goto unregister_chrdev;
    }
    my_cdev->owner = THIS_MODULE;
    my_cdev->ops = &fops;

    // 添加字符设备到内核
    if (cdev_add(my_cdev, dev_num, 1) < 0)
    {
        printk(KERN_ERR "Failed to add character device.\n");
        goto cdev_fail;
    }
    LOG_INFO("my_char_dev_init: cdev_add success\n");
    // 创建设备节点
    mychardev_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(mychardev_class))
    {
        printk(KERN_ERR "Failed to create device class.\n");
        cdev_del(my_cdev);
        free_page((unsigned long)mem_buffer);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(mychardev_class);
    }

    mychardev_device = device_create(mychardev_class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(mychardev_device))
    {
        printk(KERN_ERR "Failed to create device node.\n");
        class_destroy(mychardev_class);
        cdev_del(my_cdev);
        free_page((unsigned long)mem_buffer);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(mychardev_device);
    }

    if(create_mem_buffer() == 0)
    {
        return 0;
    }
    


cdev_fail:
    kfree(my_cdev);
    my_cdev = NULL;

unregister_chrdev:
    unregister_chrdev_region(dev_num, 1);

    return -EFAULT;
}

static void __exit my_char_dev_exit(void)
{
    // 释放缓冲区
    if (pages)
        free_pages(pages, get_order(MEM_SIZE));

    // 销毁设备节点
    device_destroy(mychardev_class, dev_num);
    // 销毁设备类
    class_destroy(mychardev_class);

    // 移除字符设备
    cdev_del(my_cdev);

    // 释放字符设备号
    unregister_chrdev_region(dev_num, 1);
}

module_init(my_char_dev_init);
module_exit(my_char_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Character Device Driver");